#include "client_handler.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

#include <json-c/json.h>      /* json */

static device_mng_list_t device_mng_list;  /* 摄像头设备链表 */
static app_mng_list_t    app_mng_list;     /* 客户端链表 */
static video_data_t      video_data;       /* 视频数据 */
static pthread_t         send_video_tid;   /* 向APP传送视频数据线程 */

static unsigned int nanopi_port = NANOPI_SOCKET_PORT_START;    /* 摄像头分配 socket 端口号 */
static unsigned int app_port    = APP_SOCKET_PORT_START;       /* 客户端分配 socket 端口号 */

/* 向APP报告控制结果 */
static void response_ctrlmsg_to_app(int fd, int err_type)
{
    int                ret;
    struct json_object *send_jobj;
    char               *j2c;

    /* 返回端口信息 {"cmd":"ctrlresponse", "result":"msg"} */
    send_jobj = json_object_new_object();
    json_object_object_add(send_jobj, "cmd", json_object_new_string("ctrlresponse")); /* key: cmd, value: ctrlresponse */

    /* 判断错误类型 */
    switch (err_type)
    {
        case GIMBAL_CTRL_FAIL_NOTMATCH:
            /* key: result, value: msg */
            json_object_object_add(send_jobj, "result", json_object_new_string("cannot matched device"));
            break;

        case GIMBAL_CTRL_FAIL_FORWARD:
            /* key: result, value: msg */
            json_object_object_add(send_jobj, "result", json_object_new_string("forward network fault"));
            break;

        default:
            /* key: result, value: msg */
            json_object_object_add(send_jobj, "result", json_object_new_string("unknow"));
            break;
    }

    j2c = (char *)json_object_get_string(send_jobj); /* 将json数据转换为 string 数据 */

    /* 将端口信息返回给 camera (通过TCP) */
    ret = send(fd, j2c, strlen(j2c), 0);

    if (ret == -1)
    {
        perror("send ctrl response to app failed");
    }

    return;
}

/* 管理链表初始化 */
void mng_list_init(void)
{
    /* 初始化设备管理链表 */
    list_init(&device_mng_list.list);

    /* 初始化APP管理链表 */
    list_init(&app_mng_list.list);

    return;
}

/* 视频数据结构体初始化 */
void video_data_t_init(void)
{
    int ret;

    /* 初始化 图片信息结构体 */
    video_data.buf = (char *)malloc(sizeof(PIC_MAX_SIZE));

    if (video_data.buf == NULL)
    {
        perror("malloc memory for pic data error");
        exit(1);
    }

    /* 初始化互斥锁 */
    ret = pthread_mutex_init(&video_data.lock, NULL);

    if (ret != 0) 
    {
        perror("mutex init failed");
        exit(1);
    }

    /* 设置 video_data 子域 */
    video_data.max_size = PIC_MAX_SIZE;   /* 限制图片的极大值 */
    video_data.isfresh  = false;          /* 图片数据是否是最新的 */

    return;
}

/*
 * 摄像头上线:
 * 1.解析json数据得到deviceid
 * 2.操作链表插入deviceid和fd
 * 3.返回摄像头端口数据
 * 4.创建udp socket
 * 5.接收视频数据
 */
static void camera_online_and_recv_video(struct json_object *jobj, int fd)
{
    int                ret;
    int                recv_size, length, set_udp_size;
    int                sockfd;
    device_mng_list_t  new_node;
    char               *j2c;
    char               *recv_buf;
    struct json_object *send_jobj;
    struct sockaddr_in nanopi_addr;
    struct sockaddr_in temp_addr;
    struct json_object *jvalue1;
    char               *cvalue1;

    jvalue1 = json_object_object_get(jobj, "deviceid"); /* 获得 deviceid key */
    cvalue1 = (char *)json_object_get_string(jvalue1);            /* 将 key 转换为字符串 */

    /* 记录设备id和套接字fd */
    new_node.fd = fd;
    memcpy(new_node.deviceid, cvalue1, strlen(cvalue1));

    /* 插入链表 */
    list_add_tail(&new_node.list, &device_mng_list.list);

    /* 返回端口信息 {"cmd":"port_info", "port":9000} */
    send_jobj = json_object_new_object();
    json_object_object_add(send_jobj, "cmd", json_object_new_string("port_info")); /* key: cmd, value: port_info */
    json_object_object_add(send_jobj, "port", json_object_new_int(nanopi_port));   /* key: cmd, value: port_info */
    nanopi_port++;

    j2c = (char *)json_object_get_string(send_jobj); /* 将json数据转换为 string 数据 */

    /* 将端口信息返回给 camera (通过TCP) */
    ret = send(fd, j2c, strlen(j2c), 0);

    if (ret == -1)
    {
        perror("tcp send to camera failed");
        exit(1);
    }

    /* 启动 udp socket 接收摄像头的视频数据 */
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    if (sockfd == -1)
    {
        perror("create udp socket to camera failed");
        exit(1);
    }

    /* 设置UDP 发送 缓冲区 */
    set_udp_size = PIC_MAX_SIZE;

    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &set_udp_size, sizeof(set_udp_size)) != 0)
    {
        perror("set udp socket recv size failed");
        exit(1);
    }

    /* 绑定端口号 (用于接收 mjpeg-streamer 的图像数据) */
    memset(&nanopi_addr, 0, sizeof(nanopi_addr));
    nanopi_addr.sin_family = AF_INET;
    nanopi_addr.sin_port = htons(nanopi_port - 1);
    nanopi_addr.sin_addr.s_addr = inet_addr(ALI_INET_ADDR);

    ret = bind(sockfd, (struct sockaddr *) &nanopi_addr, sizeof(nanopi_addr));

    if (ret == -1)
    {
        perror("bind udp socket to camera failed");
        exit(1);
    }

    /* 申请接受数据的存储空间 */
    recv_buf = (char *) malloc(PIC_MAX_SIZE);

    if (recv_buf == NULL)
    {
        perror("malloc mem for camera udp socket recv failed");
        exit(1);
    }

    /* 开始接收视频数据 (通过UDP) */

    while (1)
    {
        /* 接收来自camera的视频数据 */
        length = sizeof(temp_addr);
        memset(recv_buf, 0, PIC_MAX_SIZE);
        recv_size = recvfrom(sockfd, recv_buf, PIC_MAX_SIZE, 0, 
                             (struct sockaddr *) &temp_addr, (socklen_t *) &length);

        if (recv_size == -1)
        {
            perror("nanopi udp recv: failed");
        }
        else if (recv_size == 0)
        {
            printf("nanopi udp recv: camera is offline\n");
            break;  /* Nanopi 客户端下线，自动退出该线程 */
        }

        /* 图片缓存区加锁 */
        pthread_mutex_lock(&video_data.lock);

        /* 接收到的图片数据拷贝到图片缓冲区 */
        memset(video_data.buf, 0, recv_size);
        memcpy(video_data.buf, recv_buf, recv_size);
        video_data.size    = recv_size;           /* 记录该图片大小 */
        video_data.isfresh = true;                /* 标记图像数据是最新的 */

        /* 调试信息 */
        printf("recv a picture, size = %d\n", video_data.size);

        /* 图片缓存区解锁 */
        pthread_mutex_unlock(&video_data.lock);
    }
}

/*
 * 发送视频数据到APP:
 * 1.创建udp socket
 * 2.接收一次来自app的任意数据
 * 3.返回摄像头端口数据
 * 4.创建udp socket接收视频数据
 */
static void *send_video_to_app(void *arg)
{
    int                ret;
    int                sockfd;
    int                length;
    int                recv_size;
    struct sockaddr_in server_addr;
    struct sockaddr_in app_addr;
    char               recv_buf[128] = {0};

    /* 启动 udp socket 发送摄像头的视频数据 */
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    if (sockfd == -1)
    {
        perror("create udp socket to app failed");
        exit(1);
    }

    /* 绑定端口，用于 和 app 通信 */
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(app_port - 1);
    server_addr.sin_addr.s_addr = inet_addr(ALI_INET_ADDR);

    ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));

    if (ret == -1)
    {
        perror("bind udp socket to app failed");
        exit(1);
    }

    /* 接收 app 一帧数据，记录下 sockaddr_in */
    length = sizeof(app_addr);
    memset(&app_addr, 0, sizeof(app_addr));
    memset(recv_buf, 0, sizeof(recv_buf));
    recv_size = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, 
                         (struct sockaddr *)&app_addr, (socklen_t *)&length);

    printf("udp-test-app: %s\n", recv_buf);

    if (recv_size == -1)
    {
        perror("app udp recv: failed");
        exit(1);    /* udp通信失败，直接退出程序 */
    }
    else if (recv_size == 0)
    {
        printf("app udp recv: app is offline\n");
        return NULL;    /* app客户端下线，upd线程结束 */
    }

    /* 开始向APP发送视频数据 (通过UDP) */

    while(1)
    {
        /* 判断图像数据是否是最新的 */
        if (video_data.isfresh == true)
        {
            /* 图片缓存区加锁 */
            pthread_mutex_lock(&video_data.lock);

            /* 将图片缓存区数据发送给 APP */
            ret = sendto(sockfd, video_data.buf, video_data.size, 0, 
                         (const struct sockaddr *)&app_addr, sizeof(app_addr));

            if (ret == -1)
            {
                perror("app udp send: failed");
            }

            /* 加调试信息 */
            printf("send a picture to app, size: %d\n", video_data.size);

            /* 标记图像数据不是最新 */
            video_data.isfresh = false;

            /* 图片缓存区解锁 */
            pthread_mutex_unlock(&video_data.lock);
        }
        else
        {
            usleep(1000);   /* 延时1ms */
        }
    }
}

/*
 * APP上线:
 * 1.解析json数据得到 appid 和 deviceid
 * 2.操作链表插入 appid 和 deviceid
 * 3.返回APP端口数据
 * 4.创建send_video_to_app线程
 */
static void host_online_and_new_thread(struct json_object *jobj, int fd)
{
    int                ret;
    app_mng_list_t     new_node;
    char               *j2c;
    struct json_object *send_jobj;
    struct json_object *jvalue1;
    char               *cvalue1;
    struct json_object *jvalue2;
    char               *cvalue2;

    jvalue1 = json_object_object_get(jobj, "appid");      /* 获得 appid key */
    cvalue1 = (char *)json_object_get_string(jvalue1);           /* 将 key1 转换为字符串 */
    jvalue2 = json_object_object_get(jobj, "deviceid");   /* 获得 deviceid key */
    cvalue2 = (char *)json_object_get_string(jvalue2);           /* 将 key2 转换为字符串 */

    /* 记录设备id和套接字fd和appid */
    new_node.fd = fd;
    memcpy(new_node.appid, cvalue1, strlen(cvalue1));
    memcpy(new_node.bind_deviceid, cvalue2, strlen(cvalue2));

    /* 插入链表 */
    list_add_tail(&new_node.list, &app_mng_list.list);

    /* 返回端口信息 {"cmd":"port_info", "port":9500} */
    send_jobj = json_object_new_object();
    json_object_object_add(send_jobj, "cmd", json_object_new_string("port_info")); /* key: cmd, value: port_info */
    json_object_object_add(send_jobj, "port", json_object_new_int(app_port));      /* key: cmd, value: port_info */
    app_port++;

    j2c = (char *)json_object_get_string(send_jobj); /* 将json数据转换为 string 数据 */

    /* 将端口信息返回给 app (通过TCP) */
    ret = send(fd, j2c, strlen(j2c), 0);
    
    if (ret == -1)
    {
        perror("tcp send to app failed");
        exit(1);
    }

    /* 创建线程接收来自 APP 的控制数据 */
    ret = pthread_create(&send_video_tid, NULL, send_video_to_app, (void *)&app_port);

    /* 判断线程是否创建成功 */
    if (ret != 0)
    {
        perror("create send video to app thread failed");
        exit(1);
    }

    /* 新线程与主线程脱离 */
    pthread_detach(send_video_tid);

    return;
}

/*
 * APP控制数据解析:
 * 1.解析json数据得到 deviceid
 * 2.搜索 device_mng_list 链表，匹配到相同 deviceid 的摄像头设备
 * 3.将控制数据 (json) 转发给 匹配到的摄像头设备
 */
static void host_control_process(struct json_object *jobj, int fd)
{
    int                ret;
    char               *j2c;
    struct json_object *jvalue1;
    char               *cvalue1;
    list_t             *list;
    device_mng_list_t  *device = NULL;

    /* from qt client ctrl: {"cmd":"control", "action":1, "appid":"1000", "deviceid":"0001"} */
    jvalue1 = json_object_object_get(jobj, "deviceid");      /* 获得 appid key */
    cvalue1 = (char *)json_object_get_string(jvalue1);              /* 将 jkey1 转换为字符串 */

    /* 在 device_mng_list 中找到 deviceid 对应的 fd */
    list_for_each(list, &device_mng_list.list)
    {
        device = list_entry(list, device_mng_list_t, list);

        if (strncmp(device->deviceid, cvalue1, strlen(cvalue1) == 0))
        {
            break;  /* 匹配到 */
        }
    }

    /* 判断是否能匹配上记录中的 deviceid */
    if (device == NULL)
    {
        printf("cannot matched app and device\n");

        /* 将控制失败信息返回给APP */
        response_ctrlmsg_to_app(fd, GIMBAL_CTRL_FAIL_NOTMATCH);

        /* 匹配设备失败，直接返回 */
        return;
    }

    /* 将控制数据转发给目标设备 */
    j2c = (char *)json_object_get_string(jobj); /* 将json数据转换为 string 数据 */

    /* 将控制信息转发给找到的 camera (通过TCP) */
    ret = send(device->fd, j2c, strlen(j2c), 0);
    
    if (ret == -1)
    {
        perror("tcp send ctrl to camera failed");

        /* 将控制失败信息返回给APP */
        response_ctrlmsg_to_app(fd, GIMBAL_CTRL_FAIL_FORWARD);
    }

    return;
}

void *client_connect_handler(void *arg)
{
    int                fd = *(int *)arg;
    char               recv_buf[128] = {0};
    int                recv_size;
    struct json_object *recv_jobj;
    struct json_object *jvalue1;
    char               *cvalue1;

    /* 对客户端的数据做处理 */

    while(1)
    {
        /* 接收客户端的数据 */
        memset(recv_buf, 0, sizeof(recv_buf));
        recv_size = recv(fd, recv_buf, sizeof(recv_buf), 0);

        if (recv_size == -1)        /* 接收错误 */
        {
            perror("recv error");
            continue;               /* 继续接收 */
        }
        else if (recv_size == 0)    /* 客户端被关闭 */
        {
            printf("client -%d- is off-line\n", fd);
            break;                  /* 客户端下线，客户端线程自动结束 */
        }

        /* 打印接收到的 TCP 数据 */
        printf("recv client: %s\n", recv_buf);

        /*
         * 解析 从客户端 接收到的 json 数据:
         * from nano client: {"cmd":"info", "deviceid":"0001"}
         * from qt client on-line: {"cmd":"get_video_data", "appid":"1000", "deviceid":"0001"}
         * from qt client ctrl: {"cmd":"control", "action":1, "appid":"1000", "deviceid":"0001"}
         */
        recv_jobj = json_tokener_parse(recv_buf);
        jvalue1 = json_object_object_get(recv_jobj, "cmd");     /* 获得 cmd key */
        cvalue1 = (char *)json_object_get_string(jvalue1);  /* 将 json_parse.key1 转换为字符串 */

        /* 判断 收到的是哪种客户端的 json 数据 */
        if (strncmp(cvalue1, "info", strlen("info")) == 0)   /* 来自 nanopi 的连接 */
        {
            /* json 数据来自 nanopi */
            camera_online_and_recv_video(recv_jobj, fd);
        }
        else if (strncmp(cvalue1, "get_video_data", strlen("get_video_data")) == 0) /* 来自 APP 的连接 */
        {
            /* json 数据来自 Qt 上线 */
            host_online_and_new_thread(recv_jobj, fd);
        }
        else if (strncmp(cvalue1, "control", strlen("control")) == 0)  /* 来自 APP 的控制 */
        {
            /* json 数据来自 Qt 控制数据 */
            host_control_process(recv_jobj, fd);
        }
    }

    /* cannot be here */
    return NULL;
}
